/*
 * Copyright (C) 2013, Pavel Samko <bulldozerbsg@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the "Pavel Samko" nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

#include <windows.h>
#include <rpcdce.h>
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <stdint.h>
#include <assert.h>
#include "wcxhead.h"
#include "plug_funcs.h"

#define PIPE_BUFF_SIZE (102400)

BOOL s_init = FALSE;
HANDLE s_pipe = INVALID_HANDLE_VALUE;
STARTUPINFO s_startupInfo = 
{
	.cb = sizeof(STARTUPINFO),
	.dwFlags = STARTF_USESHOWWINDOW,
	.wShowWindow = SW_HIDE
};
PROCESS_INFORMATION s_processInformation;
uint8_t s_buff[PIPE_BUFF_SIZE];
tChangeVolProc ChangeVolProc = NULL;
tChangeVolProcW ChangeVolProcW = NULL;
tProcessDataProc ProcessDataProc = NULL;
tProcessDataProcW ProcessDataProcW = NULL;
tPkCryptProc PkCryptProc = NULL;
tPkCryptProcW PkCryptProcW = NULL;

#define CALL_PROC \
	if (func == CHANGEVOLPROC) \
	{ \
		char *arcName; GET_STR_A(p, arcName) \
		int mode; GET_INT(p, mode) \
		int back = 0; \
		if (ChangeVolProc) \
		{ \
			back = ChangeVolProc(arcName, mode); \
		} \
		p = s_buff; \
		SET_FUNC(p, CHANGEVOLPROC) \
		SET_CALLTYPE(p, CALL_ANSWER) \
		SET_INT(p, back) \
		writeSize = p - s_buff; \
		continue; \
	} else \
	if (func == PROCESSDATAPROC) \
	{ \
		char *fileName; GET_STR_A(p, fileName) \
		int size; GET_INT(p, size) \
		int back = 0; \
		if (ProcessDataProc) \
		{ \
			back = ProcessDataProc(fileName, size); \
		} \
		p = s_buff; \
		SET_FUNC(p, PROCESSDATAPROC) \
		SET_CALLTYPE(p, CALL_ANSWER) \
		SET_INT(p, back) \
		writeSize = p - s_buff; \
		continue; \
	} else \
	if (func == PKCRYPTPROC) \
	{ \
		int CryptoNumber; GET_INT(p, CryptoNumber) \
		int mode; GET_INT(p, mode) \
		char *ArchiveName; GET_STR_A(p, ArchiveName) \
		char *Password; \
		int maxlen; \
		if (mode != PK_CRYPT_LOAD_PASSWORD && \
			mode != PK_CRYPT_LOAD_PASSWORD_NO_UI) \
		{ \
			GET_STR_A(p, Password) \
			GET_INT(p, maxlen) \
		} else \
		{ \
			GET_INT(p, maxlen) \
			assert(maxlen); \
			Password = (char *)malloc(maxlen); \
			assert(Password); \
		} \
		int back = 0; \
		if (PkCryptProc) \
		{ \
			back = PkCryptProc(CryptoNumber, mode, ArchiveName, Password, maxlen); \
		} \
		p = s_buff; \
		SET_FUNC(p, PKCRYPTPROC) \
		SET_CALLTYPE(p, CALL_ANSWER) \
		if (mode == PK_CRYPT_LOAD_PASSWORD || \
			mode == PK_CRYPT_LOAD_PASSWORD_NO_UI) \
		{ \
			SET_BUFF(p, Password, maxlen * sizeof(char)) \
			free(Password); \
		} \
		SET_INT(p, back) \
		writeSize = p - s_buff; \
		continue; \
	} else \
	if (func == CHANGEVOLPROCW) \
	{ \
		wchar_t *arcName; GET_STR_W(p, arcName) \
		int mode; GET_INT(p, mode) \
		int back = 0; \
		if (ChangeVolProcW) \
		{ \
			back = ChangeVolProcW(arcName, mode); \
		} \
		p = s_buff; \
		SET_FUNC(p, CHANGEVOLPROCW) \
		SET_CALLTYPE(p, CALL_ANSWER) \
		SET_INT(p, back) \
		writeSize = p - s_buff; \
		continue; \
	} else \
	if (func == PROCESSDATAPROCW) \
	{ \
		wchar_t *fileName; GET_STR_W(p, fileName) \
		int size; GET_INT(p, size) \
		int back = 0; \
		if (ProcessDataProcW) \
		{ \
			back = ProcessDataProcW(fileName, size); \
		} \
		p = s_buff; \
		SET_FUNC(p, PROCESSDATAPROCW) \
		SET_CALLTYPE(p, CALL_ANSWER) \
		SET_INT(p, back) \
		writeSize = p - s_buff; \
		continue; \
	} else \
	if (func == PKCRYPTPROCW) \
	{ \
		int CryptoNumber; GET_INT(p, CryptoNumber) \
		int mode; GET_INT(p, mode) \
		wchar_t *ArchiveName; GET_STR_W(p, ArchiveName) \
		wchar_t *Password; \
		int maxlen; \
		if (mode != PK_CRYPT_LOAD_PASSWORD && \
			mode != PK_CRYPT_LOAD_PASSWORD_NO_UI) \
		{ \
			GET_STR_W(p, Password) \
			GET_INT(p, maxlen) \
		} else \
		{ \
			GET_INT(p, maxlen) \
			assert(maxlen); \
			Password = (wchar_t *)malloc(maxlen); \
			assert(Password); \
		} \
		int back = 0; \
		if (PkCryptProcW) \
		{ \
			back = PkCryptProcW(CryptoNumber, mode, ArchiveName, Password, maxlen); \
		} \
		p = s_buff; \
		SET_FUNC(p, PKCRYPTPROCW) \
		SET_CALLTYPE(p, CALL_ANSWER) \
		if (mode == PK_CRYPT_LOAD_PASSWORD || \
			mode == PK_CRYPT_LOAD_PASSWORD_NO_UI) \
		{ \
			SET_BUFF(p, Password, maxlen * sizeof(wchar_t)) \
			free(Password); \
		} \
		SET_INT(p, back) \
		writeSize = p - s_buff; \
		continue; \
	} \
	assert(0);

size_t strlistlen(char *list)
{
	if (list)
	{
		size_t result = 1;
		for(; list[0] || list[1]; ++list)
		{
			++result;
		}
		return result;
	}
	return 0;
}

size_t wcslistlen(wchar_t *list)
{
	if (list)
	{
		size_t result = 1;
		for(; list[0] || list[1]; ++list)
		{
			++result;
		}
		return result;
	}
	return 0;
}

__declspec(dllexport) HANDLE __stdcall OpenArchive(tOpenArchiveData *ArchiveData)
{
	if (s_init && ArchiveData)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, OPENARCHIVE)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_STR_A(p, ArchiveData->ArcName)
		SET_INT(p, ArchiveData->OpenMode)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == OPENARCHIVE);
					GET_INT(p, ArchiveData->OpenResult)
					HANDLE r; GET_HANDLE(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
		ArchiveData->OpenResult = E_NOT_SUPPORTED;
	}
	return NULL;
}

__declspec(dllexport) int __stdcall ReadHeader(HANDLE hArcData, tHeaderData *HeaderData)
{
	if (s_init && HeaderData)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, READHEADER)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_HANDLE(p, hArcData)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == READHEADER);
					char *arcName; GET_STR_A(p, arcName)
					if (arcName)
					{
						strcpy(HeaderData->ArcName, arcName);
					}
					char *fileName; GET_STR_A(p, fileName)
					if (fileName)
					{
						strcpy(HeaderData->FileName, fileName);
					}
					GET_INT(p, HeaderData->Flags)
					GET_INT(p, HeaderData->PackSize)
					GET_INT(p, HeaderData->UnpSize)
					GET_INT(p, HeaderData->HostOS)
					GET_INT(p, HeaderData->FileCRC)
					GET_INT(p, HeaderData->FileTime)
					GET_INT(p, HeaderData->UnpVer)
					GET_INT(p, HeaderData->Method)
					GET_INT(p, HeaderData->FileAttr)
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return E_NO_FILES;
}

__declspec(dllexport) int __stdcall ProcessFile(HANDLE hArcData, int Operation, char *DestPath, char *DestName)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, PROCESSFILE)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_HANDLE(p, hArcData)
		SET_INT(p, Operation)
		SET_STR_A(p, DestPath)
		SET_STR_A(p, DestName)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == PROCESSFILE);
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return 0;
}

__declspec(dllexport) int __stdcall CloseArchive(HANDLE hArcData)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, CLOSEARCHIVE)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_HANDLE(p, hArcData)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == CLOSEARCHIVE);
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return 0;
}

__declspec(dllexport) void __stdcall SetChangeVolProc(HANDLE hArcData, tChangeVolProc pChangeVolProc)
{
	ChangeVolProc = pChangeVolProc;
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, SETCHANGEVOLPROC)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_HANDLE(p, hArcData)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == SETCHANGEVOLPROC);
					return;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
}

__declspec(dllexport) void __stdcall SetProcessDataProc(HANDLE hArcData, tProcessDataProc pProcessDataProc)
{
	ProcessDataProc = pProcessDataProc;
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, SETPROCESSDATAPROC)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_HANDLE(p, hArcData)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == SETPROCESSDATAPROC);
					return;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
}

__declspec(dllexport) int __stdcall PackFiles(char *PackedFile, char *SubPath, char *SrcPath, char *AddList, int Flags)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, PACKFILES)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_STR_A(p, PackedFile)
		SET_STR_A(p, SubPath)
		SET_STR_A(p, SrcPath)
		SET_STRLIST_A(p, AddList)
		SET_INT(p, Flags)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == PACKFILES);
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return E_NOT_SUPPORTED;
}

__declspec(dllexport) int __stdcall DeleteFiles(char *PackedFile, char *DeleteList)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, DELETEFILES)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_STR_A(p, PackedFile)
		SET_STRLIST_A(p, DeleteList)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == DELETEFILES);
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return E_NOT_SUPPORTED;
}

__declspec(dllexport) int __stdcall GetPackerCaps(void)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, GETPACKERCAPS)
		SET_CALLTYPE(p, CALL_QUERY)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == GETPACKERCAPS);
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return 0;
}

__declspec(dllexport) void __stdcall ConfigurePacker(HWND Parent, HINSTANCE DllInstance)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, CONFIGUREPACKER)
		SET_CALLTYPE(p, CALL_QUERY)
		/// TODO: send HWND
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == CONFIGUREPACKER);
					return;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
}

__declspec(dllexport) int __stdcall StartMemPack(int Options, char *FileName)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, STARTMEMPACK)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_INT(p, Options)
		SET_STR_A(p, FileName)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == STARTMEMPACK);
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return 0;
}

__declspec(dllexport) int __stdcall PackToMem(int hMemPack, char *BufIn, int InLen, int *Taken, char *BufOut, int OutLen, int *Written, int SeekBy)
{
	if (s_init && Taken && Written)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, PACKTOMEM)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_INT(p, hMemPack)
		SET_BUFF(p, BufIn, InLen)
		SET_INT(p, (*Taken))
		SET_BUFF(p, BufOut, OutLen)
		SET_INT(p, (*Written))
		SET_INT(p, SeekBy)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == PACKTOMEM);
					GET_INT(p, (*Taken))
					GET_BUFF2(p, BufOut)
					GET_INT(p, (*Written))
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return E_NOT_SUPPORTED;
}

__declspec(dllexport) int __stdcall DoneMemPack(int hMemPack)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, DONEMEMPACK)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_INT(p, hMemPack)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == DONEMEMPACK);
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return E_NOT_SUPPORTED;
}

__declspec(dllexport) BOOL __stdcall CanYouHandleThisFile(char *FileName)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, CANYOUHANDLETHISFILE)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_STR_A(p, FileName)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == CANYOUHANDLETHISFILE);
					BOOL r; GET_BOOL(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return FALSE;
}

__declspec(dllexport) void __stdcall PackSetDefaultParams(PackDefaultParamStruct *dps)
{
	if (s_init && dps)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, PACKSETDEFAULTPARAMS)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_INT(p, dps->size)
		SET_DWORD(p, dps->PluginInterfaceVersionLow)
		SET_DWORD(p, dps->PluginInterfaceVersionHi)
		SET_STR_A(p, dps->DefaultIniName)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == PACKSETDEFAULTPARAMS);
					return;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
}

__declspec(dllexport) void __stdcall PkSetCryptCallback(tPkCryptProc pPkCryptProc, int CryptoNr, int Flags)
{
	PkCryptProc = pPkCryptProc;
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, PKSETCRYPTCALLBACK)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_INT(p, CryptoNr)
		SET_INT(p, Flags)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == PKSETCRYPTCALLBACK);
					return;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
}

__declspec(dllexport) int __stdcall ReadHeaderEx(HANDLE hArcData, tHeaderDataEx *HeaderDataEx)
{
	if (s_init && HeaderDataEx)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, READHEADEREX)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_HANDLE(p, hArcData)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == READHEADEREX);
					char *arcName; GET_STR_A(p, arcName)
					if (arcName)
					{
						strcpy(HeaderDataEx->ArcName, arcName);
					}
					char *fileName; GET_STR_A(p, fileName)
					if (fileName)
					{
						strcpy(HeaderDataEx->FileName, fileName);
					}
					GET_INT(p, HeaderDataEx->Flags)
					GET_UINT(p, HeaderDataEx->PackSize)
					GET_UINT(p, HeaderDataEx->PackSizeHigh)
					GET_UINT(p, HeaderDataEx->UnpSize)
					GET_UINT(p, HeaderDataEx->UnpSizeHigh)
					GET_INT(p, HeaderDataEx->HostOS)
					GET_INT(p, HeaderDataEx->FileCRC)
					GET_INT(p, HeaderDataEx->FileTime)
					GET_INT(p, HeaderDataEx->UnpVer)
					GET_INT(p, HeaderDataEx->Method)
					GET_INT(p, HeaderDataEx->FileAttr)
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return E_NO_FILES;
}

__declspec(dllexport) int __stdcall GetBackgroundFlags(void)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, GETBACKGROUNDFLAGS)
		SET_CALLTYPE(p, CALL_QUERY)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == GETBACKGROUNDFLAGS);
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return 0;
}

__declspec(dllexport) HANDLE __stdcall OpenArchiveW(tOpenArchiveDataW *ArchiveData)
{
	if (s_init && ArchiveData)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, OPENARCHIVEW)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_STR_W(p, ArchiveData->ArcName)
		SET_INT(p, ArchiveData->OpenMode)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == OPENARCHIVEW);
					GET_INT(p, ArchiveData->OpenResult)
					HANDLE r; GET_HANDLE(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
		ArchiveData->OpenResult = E_NOT_SUPPORTED;
	}
	return NULL;
}

__declspec(dllexport) int __stdcall ReadHeaderExW(HANDLE hArcData, tHeaderDataExW *HeaderDataEx)
{
	if (s_init && HeaderDataEx)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, READHEADEREXW)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_HANDLE(p, hArcData)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == READHEADEREXW);
					wchar_t *arcName; GET_STR_W(p, arcName)
					if (arcName)
					{
						wcscpy(HeaderDataEx->ArcName, arcName);
					}
					wchar_t *fileName; GET_STR_W(p, fileName)
					if (fileName)
					{
						wcscpy(HeaderDataEx->FileName, fileName);
					}
					GET_INT(p, HeaderDataEx->Flags)
					GET_UINT(p, HeaderDataEx->PackSize)
					GET_UINT(p, HeaderDataEx->PackSizeHigh)
					GET_UINT(p, HeaderDataEx->UnpSize)
					GET_UINT(p, HeaderDataEx->UnpSizeHigh)
					GET_INT(p, HeaderDataEx->HostOS)
					GET_INT(p, HeaderDataEx->FileCRC)
					GET_INT(p, HeaderDataEx->FileTime)
					GET_INT(p, HeaderDataEx->UnpVer)
					GET_INT(p, HeaderDataEx->Method)
					GET_INT(p, HeaderDataEx->FileAttr)
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return E_NO_FILES;
}

__declspec(dllexport) int __stdcall ProcessFileW(HANDLE hArcData, int Operation, wchar_t *DestPath, wchar_t *DestName)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, PROCESSFILEW)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_HANDLE(p, hArcData)
		SET_INT(p, Operation)
		SET_STR_W(p, DestPath)
		SET_STR_W(p, DestName)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == PROCESSFILEW);
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return 0;
}

__declspec(dllexport) void __stdcall SetChangeVolProcW(HANDLE hArcData, tChangeVolProcW pChangeVolProc)
{
	ChangeVolProcW = pChangeVolProc;
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, SETCHANGEVOLPROCW)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_HANDLE(p, hArcData)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == SETCHANGEVOLPROCW);
					return;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
}

__declspec(dllexport) void __stdcall SetProcessDataProcW(HANDLE hArcData, tProcessDataProcW pProcessDataProc)
{
	ProcessDataProcW = pProcessDataProc;
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, SETPROCESSDATAPROCW)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_HANDLE(p, hArcData)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == SETPROCESSDATAPROCW);
					return;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
}

__declspec(dllexport) int __stdcall PackFilesW(wchar_t *PackedFile, wchar_t *SubPath, wchar_t *SrcPath, wchar_t *AddList, int Flags)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, PACKFILESW)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_STR_W(p, PackedFile)
		SET_STR_W(p, SubPath)
		SET_STR_W(p, SrcPath)
		SET_STRLIST_W(p, AddList)
		SET_INT(p, Flags)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == PACKFILESW);
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return E_NOT_SUPPORTED;
}

__declspec(dllexport) int __stdcall DeleteFilesW(wchar_t *PackedFile, wchar_t *DeleteList)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, DELETEFILESW)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_STR_W(p, PackedFile)
		SET_STRLIST_W(p, DeleteList)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == DELETEFILESW);
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return E_NOT_SUPPORTED;
}

__declspec(dllexport) int __stdcall StartMemPackW(int Options, wchar_t *FileName)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, STARTMEMPACKW)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_INT(p, Options)
		SET_STR_W(p, FileName)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == STARTMEMPACKW);
					int r; GET_INT(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return 0;
}

__declspec(dllexport) BOOL __stdcall CanYouHandleThisFileW(wchar_t *FileName)
{
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, CANYOUHANDLETHISFILEW)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_STR_W(p, FileName)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == CANYOUHANDLETHISFILEW);
					BOOL r; GET_BOOL(p, r)
					// result
					return r;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
	return FALSE;
}

__declspec(dllexport) void __stdcall PkSetCryptCallbackW(tPkCryptProcW pPkCryptProc, int CryptoNr, int Flags)
{
	PkCryptProcW = pPkCryptProc;
	if (s_init)
	{
		// serialize
		uint8_t *p = s_buff;
		SET_FUNC(p, PKSETCRYPTCALLBACKW)
		SET_CALLTYPE(p, CALL_QUERY)
		SET_INT(p, CryptoNr)
		SET_INT(p, Flags)
		// send
		DWORD writeSize = p - s_buff;
		DWORD writedSize;
		while (WriteFile(s_pipe, s_buff, writeSize, &writedSize, NULL))
		{
			assert(writeSize == writedSize);
			// recv
			DWORD readedSize;
			if (ReadFile(s_pipe, s_buff, PIPE_BUFF_SIZE, &readedSize, NULL))
			{
				// deserialize
				uint8_t *p = s_buff;
				uint8_t func; GET_FUNC(p, func)
				uint8_t callType; GET_CALLTYPE(p, callType)
				if (callType == CALL_ANSWER)
				{
					assert(func == PKSETCRYPTCALLBACKW);
					return;
				} else
				if (callType == CALL_QUERY)
				{
					CALL_PROC
				}
				assert(0);
			}
		}
	}
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)  
{
	switch (fdwReason)
	{
		case DLL_PROCESS_ATTACH:
		{
			// generate unique pipe name
			UUID uuid;
			RPC_STATUS r = UuidCreate(&uuid);
			if (r != RPC_S_OK)
			{
				goto close_false;
			}
			unsigned char *uuid_s = NULL;
			r = UuidToStringA(&uuid, &uuid_s);
			if (r != RPC_S_OK)
			{
				goto close_false;
			}
			char pipeName[48];
			snprintf(pipeName, 48, "\\\\.\\pipe\\{%s}", uuid_s);
			RpcStringFreeA(&uuid_s);
			// create lpc pipe
			s_pipe = CreateNamedPipeA(pipeName,
				PIPE_ACCESS_DUPLEX,
				PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
                1,
                PIPE_BUFF_SIZE, PIPE_BUFF_SIZE,
                0,
                NULL);
			if (s_pipe == INVALID_HANDLE_VALUE)
			{
				goto close_false;
			}
			// create bridge application path
			char bridgeDll[MAX_PATH + 1];
			bridgeDll[MAX_PATH] = 0; // For Win XP if buff to small (see MSDN:GetModuleFileName)
			GetModuleFileNameA(hinstDLL, bridgeDll, MAX_PATH);
			char *lastDot = strrchr(bridgeDll, '.');
			if (lastDot)
			{
				lastDot[0] = 0;
				++lastDot;
				if (strlen(lastDot) > 3)
				{
					lastDot[3] = 0;
				}
			}
			char bridgeApp[MAX_PATH];
			snprintf(bridgeApp, MAX_PATH, "%s.exe %s %s", bridgeDll, lastDot? lastDot : "dll", pipeName);
			// create process
			if (!CreateProcessA(NULL,
				bridgeApp,
				NULL,
				NULL,
				FALSE,
				0,
				NULL,
				NULL,
				&s_startupInfo,
				&s_processInformation))
			{
				goto close_pipe;
			}
			// connect client
			if (!ConnectNamedPipe(s_pipe, NULL))
			{
				goto close_process;
			}
			s_init = TRUE;
			
			return TRUE;
close_process:
			TerminateProcess(s_processInformation.hProcess, 0);
			CloseHandle(s_startupInfo.hStdInput);
			CloseHandle(s_startupInfo.hStdOutput);
			CloseHandle(s_startupInfo.hStdError);
			CloseHandle(s_processInformation.hProcess);
			CloseHandle(s_processInformation.hThread);
close_pipe:
			CloseHandle(s_pipe);
			s_pipe = INVALID_HANDLE_VALUE;
close_false:
			return FALSE;
		}
		case DLL_PROCESS_DETACH:
		{
			if (s_init)
			{
				DisconnectNamedPipe(s_pipe);
				TerminateProcess(s_processInformation.hProcess, 0);
				CloseHandle(s_startupInfo.hStdInput);
				CloseHandle(s_startupInfo.hStdOutput);
				CloseHandle(s_startupInfo.hStdError);
				CloseHandle(s_processInformation.hProcess);
				CloseHandle(s_processInformation.hThread);
				s_init = FALSE;
			}
			if (s_pipe != INVALID_HANDLE_VALUE)
			{
				CloseHandle(s_pipe);
				s_pipe = INVALID_HANDLE_VALUE;
			}

			return TRUE;
		}  
	}    
	return TRUE;  
}
